from collections import defaultdict, Counter
from typing import List, Dict, Tuple, Optional, Any
from datetime import datetime


def extract_module(path: str) -> Optional[str]:
    """
    从文件路径提取模块名（保留路径格式）
    :param path: 文件路径 (e.g., 'src/main/java/com/example/Service1.java')
    :return: 模块名 (e.g., 'com/example/Service1')，非源码文件返回None
    """
    if path.startswith('src/main/java/'):
        return path.replace('src/main/java/', '')[:-5]  # 移除.java扩展名
    elif path.startswith('src/test/java/'):
        module_path = path.replace('src/test/java/', '')[:-5]
        return module_path
    return None


def is_test_file(path: str) -> bool:
    """ 检查文件路径是否属于测试文件 """
    return path.startswith('src/test/java/')


def is_match(impl_module: str, test_module: str) -> bool:
    """
    判断测试类是否对应实现类，遵循Java命名规范
    :param impl_module: 实现类模块路径 (e.g., 'com/example/Service1')
    :param test_module: 测试类模块路径 (e.g., 'com/example/Service1Test')
    :return: 如果测试类对应实现类返回True
    """
    # 常见的Java测试类命名规范
    test_suffixes = ['Test', 'Tests', 'IT', 'IntegrationTest']

    # 检查是否是标准的测试类命名
    for suffix in test_suffixes:
        if test_module.endswith(suffix) and test_module[:-len(suffix)] == impl_module:
            return True

    # 检查是否是内部类的测试
    if 'Test' in test_module and impl_module in test_module:
        # 简单的启发式规则：如果实现类名是测试类名的前缀
        impl_class_name = impl_module.split('/')[-1]
        test_class_name = test_module.split('/')[-1]
        if test_class_name.startswith(impl_class_name):
            return True

    return False


def find_dev_test_cycles(commits: List[Dict], k: int = 10) -> List[Tuple]:
    """
    检测高频模块的开发-测试周期
    :param commits: 提交记录列表，每个提交包含:
                    - 'commit_id': 提交ID
                    - 'updated_time': 提交时间（字符串，格式为'%Y-%m-%d'）
                    - 'files': 修改文件路径列表
    :param k: 提取的高频模块数量
    :return: 周期列表，每个周期为元组 (src_commit, test_commit, module)
    """
    # 步骤1: 统计模块频次（仅统计非测试文件）
    module_freq = defaultdict(int)
    for commit in commits:
        modules_in_commit = set()
        for file_path in commit['files']:
            module = extract_module(file_path)
            if module and not is_test_file(file_path):
                modules_in_commit.add(module)
        for module in modules_in_commit:
            module_freq[module] += 1

    # 选择Top-K高频模块
    if not module_freq:
        return []

    top_modules = [mod for mod, _ in Counter(module_freq).most_common(k)]

    # 步骤2: 处理每个高频模块
    cycles = []
    for module in top_modules:
        # 收集涉及当前模块的提交
        module_commits = []
        for commit in commits:
            has_src = False
            has_test = False
            test_modules_in_commit = set()

            for file_path in commit['files']:
                mod = extract_module(file_path)
                if mod == module and not is_test_file(file_path):
                    has_src = True
                elif is_test_file(file_path) and is_match(module, mod):
                    has_test = True
                    test_modules_in_commit.add(mod)

            if has_src or has_test:
                # 确保时间可以比较
                if isinstance(commit['updated_time'], str):
                    time_obj = datetime.strptime(commit['updated_time'], '%Y-%m-%d')
                else:
                    time_obj = commit['updated_time']

                module_commits.append({
                    'commit_id': commit['commit_id'],
                    'time': time_obj,
                    'has_src': has_src,
                    'has_test': has_test,
                    'test_modules': test_modules_in_commit
                })

        # 按提交时间排序
        module_commits.sort(key=lambda x: x['time'])

        # 步骤3: 改进的配对算法 - 支持多个周期
        # 存储所有检测到的周期
        module_cycles = []
        # 存储当前待匹配的src提交
        current_src = None
        # 存储当前待匹配的test提交（最后一个test）
        last_test = None

        # 遍历所有提交
        for i, commit in enumerate(module_commits):
            # 处理混合提交（同时包含src和test）
            if commit['has_src']:
                # 如果遇到新的src提交，尝试匹配之前的src和test
                if current_src and last_test:
                    # 确保test提交在src提交之后
                    if last_test['time'] >= current_src['time']:
                        module_cycles.append((current_src, last_test, module))

                # 更新当前待匹配的src提交
                current_src = commit
                # 重置test记录
                last_test = None

            if commit['has_test']:
                # 更新最后一个test提交
                last_test = commit

                # 如果当前没有待匹配的src提交，但遇到了test提交
                # 这可能意味着测试是针对之前的src提交的
                if not current_src:
                    # 尝试找到之前的src提交
                    for j in range(i - 1, -1, -1):
                        prev_commit = module_commits[j]
                        if prev_commit['has_src']:
                            # 确保test提交在src提交之后
                            if commit['time'] >= prev_commit['time']:
                                module_cycles.append((prev_commit, commit, module))
                            break

        # 处理最后的匹配（如果存在）
        if current_src and last_test:
            # 确保test提交在src提交之后
            if last_test['time'] >= current_src['time']:
                module_cycles.append((current_src, last_test, module))

        # 添加到总周期列表
        cycles.extend(module_cycles)

    return cycles


# 使用示例
if __name__ == "__main__":
    # 模拟输入数据（一个模块有多个周期）
    sample_commits = [
        {
            'commit_id': 'src1',
            'updated_time': '2023-01-01',
            'files': ['src/main/java/com/example/Service1.java']
        },
        {
            'commit_id': 'test1',
            'updated_time': '2023-01-02',
            'files': ['src/test/java/com/example/Service1Test.java']
        },
        {
            'commit_id': 'src2',
            'updated_time': '2023-01-03',
            'files': ['src/main/java/com/example/Service1.java']
        },
        {
            'commit_id': 'test2',
            'updated_time': '2023-01-04',
            'files': ['src/test/java/com/example/Service1Test.java']
        },
        {
            'commit_id': 'src3',
            'updated_time': '2023-01-05',
            'files': ['src/main/java/com/example/Service2.java']
        },
        {
            'commit_id': 'test3',
            'updated_time': '2023-01-06',
            'files': ['src/test/java/com/example/Service2Test.java']
        },
        {
            'commit_id': 'src4',
            'updated_time': '2023-01-07',
            'files': ['src/main/java/com/example/Service2.java']
        },
        {
            'commit_id': 'test4',
            'updated_time': '2023-01-08',
            'files': ['src/test/java/com/example/Service2Test.java']
        }
    ]

    print("检测到的开发-测试周期:")
    result = find_dev_test_cycles(sample_commits, k=5)
    for src, test, module in result:
        print(f"模块 {module}:")
        print(f"  开发提交: {src['commit_id']} @ {src['time'].strftime('%Y-%m-%d')}")
        print(f"  测试提交: {test['commit_id']} @ {test['time'].strftime('%Y-%m-%d')}")
        if test['test_modules']:
            print(f"  测试模块: {', '.join(test['test_modules'])}")

    print("\n预期结果:")
    print("Service1模块应该有2个周期: (src1, test1) 和 (src2, test2)")
    print("Service2模块应该有2个周期: (src3, test3) 和 (src4, test4)")